package com.greentreecs.fms.service.impl;

import com.greentreecs.fms.constant.TableQueryParamConstant;
import com.greentreecs.fms.mapper.QueryIncOutcBalMapper;
import com.greentreecs.fms.pojo.domain.FeeGenerateDO;
import com.greentreecs.fms.pojo.domain.IncGenerateDO;
import com.greentreecs.fms.pojo.domain.IncOutBalRecDO;
import com.greentreecs.fms.pojo.domain.OutcGenerateDO;
import com.greentreecs.fms.pojo.dto.FeeRelevantTableDTO;
import com.greentreecs.fms.pojo.dto.IncOutcBalanceDTO;
import com.greentreecs.fms.service.AccountingService;
import com.greentreecs.fms.util.CopyUtil;
import com.greentreecs.fms.util.ReadExcelUtil;
import com.greentreecs.fms.util.TableQueryUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * @author 刀氏春秋
 * @date 2023/11/23
 */
@Service
public class AccountingServiceImpl implements AccountingService {
    private final static Logger logger = LoggerFactory.getLogger(AccountingService.class);

    @Autowired
    private QueryIncOutcBalMapper queryDataMapper;

    /**
     * 将其他记录同收支总表合并
     *
     * @param feeGenes 收支总表
     * @param incGenes 收入总表
     */
    private void mergeIncGeneToFeeGene(List<FeeGenerateDO> feeGenes, List<IncGenerateDO> incGenes) {
        // 对每条收入记录，匹配是否已于收支表中存在，若不存在则将其填入
        incGenes.forEach(incRec -> {
            // 如果不存在这一条记录，说明收支总表缺项，需要补充
            if (!TableQueryUtil.isIncRecExistInFeeGenesTable(feeGenes, incRec)) {
                // 利用copyProperties补充收入条目
                FeeGenerateDO supFeeRec = new FeeGenerateDO();
                BeanUtils.copyProperties(incRec, supFeeRec);
                feeGenes.add(supFeeRec);
            }
        });
    }

    /**
     * 将支出表同收支总表合并
     *
     * @param feeGenes  收支总表
     * @param outcGenes 支出总表
     */
    private void mergeOutcGeneToFeeGene(List<FeeGenerateDO> feeGenes, List<OutcGenerateDO> outcGenes) {
        // 对于每条支出记录，匹配是否于收支表中存在，若不存在则将其填入，若存在，考虑根据备注信息增补收支记录
        outcGenes.forEach(outcRec -> {
            // 不看携程美团等预付款记录，因为没有收入条目对应，默认无需核对
            if (TableQueryUtil.isNormalRecord(outcRec)) {
                if (!TableQueryUtil.isOutcRecExistInFeeGenesTable(feeGenes, outcRec)) {
                    // 利用copyProperties补充收入条目
                    FeeGenerateDO supFeeRec = new FeeGenerateDO();
                    BeanUtils.copyProperties(outcRec, supFeeRec);
                    feeGenes.add(supFeeRec);
                }
            }
        });
    }

    /**
     * 收入支出相抵，检查平账，并输出不平帐账户信息
     *
     * @param feeGenes 收支总表
     * @return 收入支出平衡表
     */
    private List<IncOutcBalanceDTO> calIncAndOutcBalance(List<FeeGenerateDO> feeGenes) {
        // 构建哈希表用以记录收支平衡，以账号为Key
        HashMap<String, IncOutcBalanceDTO> balanceRecs = new HashMap<>();
        for (FeeGenerateDO feeRec : feeGenes) {
            // 首先仅考虑科目为房金和押金的记录
            double bill;
            // xx房金为支出，记录为负；押金xx为收入，记录为正
            if (feeRec.getType().endsWith("房金")) {
                bill = -Double.parseDouble(feeRec.getBill());
            } else if (feeRec.getType().startsWith("押金")
                    || feeRec.getType().equals("中央预付金额") || feeRec.getType().equals("退人民币")) {
                bill = Double.parseDouble(feeRec.getBill());
            } else {
                // 其他情况平账时不考虑
                continue;
            }
            // 检查当前账户是否已缓存
            if (balanceRecs.containsKey(feeRec.getAccountNo())) {
                // 缓存过该账户，直接记账
                balanceRecs.get(feeRec.getAccountNo()).calBalance(bill);
            } else {
                // 未缓存过该账户，新建收支平衡记录对象，并存储相关信息
                IncOutcBalanceDTO balanceRec = new IncOutcBalanceDTO();
                BeanUtils.copyProperties(feeRec, balanceRec);
                // 设定账户初始余额
                balanceRec.setBalance(bill);
                // 设定平账计算日期
                balanceRec.setAccountDate(LocalDate.now().toString());
                balanceRecs.put(feeRec.getAccountNo(), balanceRec);
            }
        }

        // 对于最终结果，过滤掉已平账户，并对未平账户按照姓名字母顺序排列
        return balanceRecs.values().stream().filter(balRec -> Math.abs(balRec.getBalance()) > 1e-3).toList();
    }

    @Override
    public FeeRelevantTableDTO fetchAllOriginTable(String time) {
        FeeRelevantTableDTO tableGroup = new FeeRelevantTableDTO();
        // 设置时间参数
        TableQueryParamConstant.setAllDateTime(time);
        // 读取报表并导入DTO对象中
        // 读取全部报表数据，分别为支出收入总表，收入表，支出表，以及历史平衡记录表
        // 此处使用stream的map方法配合lambda表达式完成批量式的类型转换，解决了某一List向另一List的转化
        List<FeeGenerateDO> feeGenes = new ReadExcelUtil<FeeGenerateDO>()
                .fetchExcelTableData(TableQueryParamConstant.DAY_FEE_GEN_PARAM_TEMP);
        if (feeGenes == null) return null;
        else feeGenes = new LinkedList<>(feeGenes.stream().filter(TableQueryUtil::isNormalRecord).toList());

        List<IncGenerateDO> incGenes = new ReadExcelUtil<IncGenerateDO>()
                .fetchExcelTableData(TableQueryParamConstant.DAY_INC_GEN_PARAM_TEMP);
        if (incGenes == null) return null;
        else incGenes = new LinkedList<>(incGenes.stream().filter(TableQueryUtil::isNormalRecord).toList());

        List<OutcGenerateDO> outcGenes = new ReadExcelUtil<OutcGenerateDO>()
                .fetchExcelTableData(TableQueryParamConstant.DAY_OUTC_GEN_PARAM_TEMP);
        if (outcGenes == null) return null;
        else outcGenes = new LinkedList<>(outcGenes.stream().filter(TableQueryUtil::isNormalRecord).toList());

        List<IncOutcBalanceDTO> historyBal = new ReadExcelUtil<IncOutcBalanceDTO>()
                .fetchExcelTableData(TableQueryParamConstant.HISTORY_INC_OUTC_BAL_PARAM_TEMP);
        if (historyBal == null) historyBal = new LinkedList<>();

        // 整合四个数据表
        tableGroup.setFeeGenes(feeGenes);
        tableGroup.setIncGenes(incGenes);
        tableGroup.setOutcGenes(outcGenes);
        tableGroup.setHistoryBal(historyBal);
        return tableGroup;
    }

    @Override
    public List<IncOutcBalanceDTO> checkIncOutcBalance(String time) {
        // 设定时间参数
        TableQueryParamConstant.setAllDateTime(time);
        FeeRelevantTableDTO tableGroup = fetchAllOriginTable(time);
        if (tableGroup == null) {
            logger.error("因三表中存在读取失败情况，故中止分析");
            return null;
        }

        logger.info("收支表、收入表、支出表数据读取完毕");
        // 思路是将三个数据源数据合并到一个中，即将收支数据统一归纳到FeeGenerate这一个列表上
        // 完成收入记录合并
        this.mergeIncGeneToFeeGene(tableGroup.getFeeGenes(), tableGroup.getIncGenes());
        logger.info("已完成收入记录合并");
        // 完成支出记录合并
        this.mergeOutcGeneToFeeGene(tableGroup.getFeeGenes(), tableGroup.getOutcGenes());
        logger.info("已完成支出记录合并");
        // 开始计算收支平衡
        return this.calIncAndOutcBalance(tableGroup.getFeeGenes());
    }

    @Override
    public List<IncOutcBalanceDTO> mergeIncOutcBalToHistory(List<IncOutcBalanceDTO> toBeMergedData) {
        List<IncOutcBalanceDTO> historyData = new ReadExcelUtil<IncOutcBalanceDTO>()
                .fetchExcelTableData(TableQueryParamConstant.HISTORY_INC_OUTC_BAL_PARAM_TEMP);
        if (toBeMergedData == null) {
            // 说明传入参数为空，直接返回避免空指针错误
            return null;
        } else if (historyData == null) {
            // 说明历史表未被读取到，此时直接将当前结果合并即可
            queryDataMapper.insertIncOutcBalRecList(
                    new CopyUtil<IncOutcBalanceDTO, IncOutBalRecDO>()
                            .copyListProperties(toBeMergedData, IncOutBalRecDO::new));
            return toBeMergedData;
        } else {
            // 未避免线程不安全，不在遍历历史记录表的同时修改该表
            List<IncOutcBalanceDTO> noMatchedData = new LinkedList<>(); // 未匹配数据
            List<IncOutcBalanceDTO> toBeDelData = new LinkedList<>(); // 待移除数据
            List<IncOutcBalanceDTO> matchedData = new LinkedList<>(); // 待更新数据
            // 如果能读取到历史记录
            toBeMergedData.forEach(balRec -> {
                // 根据账号匹配，一个人如果开了多个账号，每个账号独立记录
                AtomicBoolean isMatched = new AtomicBoolean(false);
                historyData.forEach(hisRec -> {
                    // 同历史记录比对，如果有相同账户，将当前新记录合并（更新历史记录）
                    if (balRec.getAccountNo().equals(hisRec.getAccountNo())) {
                        // 合并金额
                        hisRec.calBalance(balRec.getBalance());
                        // 合并后如果金额为0，则将其放入待删除表中
                        if (Math.abs(hisRec.getBalance()) < 1e-2) toBeDelData.add(hisRec);
                        // 合并最后负责人
                        hisRec.setOperator(balRec.getOperator());
                        // 合并最后入住房号
                        hisRec.setRoomNo(balRec.getRoomNo());
                        // 合并平账日期
                        hisRec.setAccountDate(balRec.getAccountDate());
                        isMatched.set(true);
                        matchedData.add(hisRec);
                    }
                });
                // 判断是否匹配，如果没有匹配到任何账户，则直接填入
                if (!isMatched.get()) noMatchedData.add(balRec);
            });
            if (matchedData.size() != 0)
                queryDataMapper.updateIncOutcBalRecList(
                        new CopyUtil<IncOutcBalanceDTO, IncOutBalRecDO>()
                                .copyListProperties(matchedData, IncOutBalRecDO::new));
            // 此时已能匹配账号的记录已经合并完毕了，未能匹配的插入到历史表尾部即可
            historyData.addAll(noMatchedData);
            // 未匹配的，可以直接insert，因为无主键冲突
            if (noMatchedData.size() != 0)
                queryDataMapper.insertIncOutcBalRecList(
                        new CopyUtil<IncOutcBalanceDTO, IncOutBalRecDO>()
                                .copyListProperties(noMatchedData, IncOutBalRecDO::new));
            // 待删除记录（已平账账户），直接删除即可
            toBeDelData.forEach(historyData::remove);
            if (toBeDelData.size() != 0)
                queryDataMapper.deleteIncOutcBalRecList(
                        new CopyUtil<IncOutcBalanceDTO, IncOutBalRecDO>()
                                .copyListProperties(toBeDelData, IncOutBalRecDO::new));
            return historyData;
        }
    }

    @Override
    public List<IncOutcBalanceDTO> deleteHistoryRecBal(List<IncOutcBalanceDTO> toBeSave) {
        if (toBeSave.size() > 0)
            queryDataMapper.deleteHistoryRecList(
                    new CopyUtil<IncOutcBalanceDTO, IncOutBalRecDO>()
                            .copyListProperties(toBeSave, IncOutBalRecDO::new));
        else
            queryDataMapper.truncateHistoryRecList();
        return toBeSave;
    }
}
